package com.unitd.frame.spring.prop;

import com.unitd.frame.comm.utils.CharsetUtils;
import com.unitd.frame.comm.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @desc 资源文件加载工具类(可用于读取普通配置文件和配置中心的配置信息)
 * @filename PropertiesUtils.java
 * @copyright www.unitd.com
 * @author Hudan
 * @version 1.0
 * @date 2016/11/13
 */
public final class ResourceUtils {

	private static Logger logger = LoggerFactory.getLogger(ResourceUtils.class.getName());

	/** 读取属性文件的属性时,给属性增加响应的前缀 */
	private static final String PLACEHOLDER_PREFIX = "${";
	/** 读取属性文件的属性时,给属性增加响应的后缀 */
	private static final String PLACEHOLDER_SUFFIX = "}";

	/** 是否已进行初始化加载 */
	private static boolean inited;
	/** 属性配置文件是否已进行过合并 */
	private static boolean merged;
	/** 属性配置文件的所有属性配置信息 */
	private final static Properties ALL_PROPERTIES = new Properties();

	/** 获取单个Prop属性对象的方法 */
	private static Method envHelperGetPropertiesMethod;
	/** 获取所有Prop属性对象的方法 */
	private static Method envHelperGetAllPropertiesMethod;

	/**
	 * @desc 加载方法,获取属性配置文件的属性
	 */
	private synchronized static void load() {
		if (inited) {
			return;
		}

		try {
			// 获取EnvironmentHelper类对象
			Class<?> threadClazz = Class.forName("com.unitd.frame.spring.helper.EnvironmentHelper");
			// 通过反射获取EnvironmentHelper类的getProperty方法
			envHelperGetPropertiesMethod = threadClazz.getMethod("getProperty", String.class);
			// 通过反射获取EnvironmentHelper类的getAllProperties方法
			envHelperGetAllPropertiesMethod = threadClazz.getMethod("getAllProperties", String.class);
		} catch (Exception e) {
			e.printStackTrace();
		}

		merged = envHelperGetAllPropertiesMethod == null && envHelperGetPropertiesMethod == null;
		try {
			String classpath = System.getProperty("java.class.path");
			logger.info("CLASSPATH: " + classpath);
			URL url = Thread.currentThread().getContextClassLoader().getResource("");
			if (url == null) url = ResourceUtils.class.getResource("");
			if (url == null) return;

			// 取得URL的协议信息
			String protocol = url.getProtocol();

			if ("file".equalsIgnoreCase(protocol)) {
				System.out.println("调用 loadPropertiesFromFile方法,加载属性配置信息, 属性的原始加载路径为 [" + url.getPath() + "]");
				File parent = new File(url.getPath());

				if (!parent.exists()) {
					System.err.println("loadPropertiesFromFile_error,dir not found");
				} else {
					loadPropertiesFromFile(parent);
				}
			} else if ("jar".equalsIgnoreCase(protocol)) {
				loadPropertiesFromJarFile(url);
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			inited = true;
		}
	}

	/**
	 * @desc 加载指定路径下的properties属性文件
	 * @param proFilePath 属性文件路径
	 */
	private static void loadPropertiesFromFile(File proFilePath) throws IOException {
		File[] files = proFilePath.listFiles();
		if(files==null || files.length==0) {
			return;
		}

		for (File file : files) {
			if (file.isDirectory()) {
				loadPropertiesFromFile(file);
			} else {
				String path = file.getPath();
				if (path.endsWith("properties")) {
					if (path.contains("i18n")) continue;
					Properties p = new Properties();
					p.load(new FileReader(file));

					ALL_PROPERTIES.putAll(p);
				}
			}
		}
	}

	/**
	 * @desc 加载指定URL或者jar包下的properties属性文件
	 * @param url 属性文件所在URL或者jar包路径
	 */
	private static void loadPropertiesFromJarFile(URL url) throws IOException {

		logger.info("loadPropertiesFromJarFile,origin:" + url.toString());
		String jarFilePath = url.getFile();
		if (jarFilePath.contains("war!")) {
			jarFilePath = StringUtils.splitByWholeSeparator(jarFilePath, "war!")[0] + "war";
		} else if (jarFilePath.contains("jar!")) {
			jarFilePath = StringUtils.splitByWholeSeparator(jarFilePath, "jar!")[0] + "jar";
		}
		jarFilePath = jarFilePath.substring("file:".length());
		jarFilePath = java.net.URLDecoder.decode(jarFilePath, CharsetUtils.UTF_8.name());
		logger.info("loadPropertiesFromJarFile,real:" + jarFilePath);
		JarFile jarFile = new JarFile(jarFilePath);

		Enumeration<JarEntry> entries = jarFile.entries();
		while (entries.hasMoreElements()) {
			JarEntry entry = entries.nextElement();
			if (entry.getName().endsWith(".properties")) {
				InputStream inputStream = jarFile.getInputStream(jarFile.getJarEntry(entry.getName()));
				Properties properties = new Properties();
				properties.load(inputStream);
				try {
					inputStream.close();
				} catch (Exception e) {
					logger.error("读取properties属性文件出错:" + e.getLocalizedMessage());
				}

				ALL_PROPERTIES.putAll(properties);
			}
		}
		jarFile.close();
	}


	@SuppressWarnings("unchecked")
	private synchronized static void mergeWithEnvironment() {
		if (merged) return;

		Map<String, Object> envProperties;
		if (envHelperGetAllPropertiesMethod != null) {
			try {
				envProperties = (Map<String, Object>) envHelperGetAllPropertiesMethod.invoke(null, "");
				if (envProperties == null || envProperties.isEmpty()) return;
				for (String key : envProperties.keySet()) {
					ALL_PROPERTIES.setProperty(key, envProperties.get(key).toString());
				}
				merged = true;
			} catch (Exception e) {
				e.printStackTrace();
			}
			return;
		}

		Set<Entry<Object, Object>> entrySet = ALL_PROPERTIES.entrySet();
		for (Entry<Object, Object> entry : entrySet) {
			Object value;
			try {
				value = envHelperGetPropertiesMethod.invoke(null, entry.getKey());
				if (value != null) {
					ALL_PROPERTIES.setProperty(entry.getKey().toString(), value.toString());
				}
			} catch (Exception e) {
				return;
			}
		}

		merged = true;
	}

	/**
	 * @desc 获取所有配置的副本
	 * @return Properties
	 */
	public static Properties getAllProperties() {
		return getAllProperties(null);
	}

	/**
	 * @desc 获取指定前缀的配置
	 * @param prefix 指定的属性的前缀
	 * @return 属性对象信息
	 */
	public static Properties getAllProperties(String prefix) {
		if (!inited) {
			load();
		}
		if (!merged) {
			mergeWithEnvironment();
		}

		Properties properties = new Properties();
		Set<Entry<Object, Object>> entrySet = ALL_PROPERTIES.entrySet();
		for (Entry<Object, Object> entry : entrySet) {
			if (StringUtils.isBlank(prefix) || entry.getKey().toString().startsWith(prefix)) {
				properties.put(entry.getKey(), entry.getValue());
			}
		}
		return properties;
	}

	public static String get(String key, String... defaultValue) {
		if (defaultValue != null && defaultValue.length > 0 && defaultValue[0] != null) {
			return getProperty(key, defaultValue[0]);
		} else {
			return getProperty(key);
		}
	}

	public static String getProperty(String key) {
		return getProperty(key, null);
	}

	public static String getAndValidateProperty(String key) {
		String value = getProperty(key, null);
		if (StringUtils.isBlank(value)) {
			throw new IllegalArgumentException(String.format("Property for key:%s not exists", key));
		}
		return value;
	}

	public static String getProperty(String key, String defaultValue) {
		if (!inited) {
			load();
		}
		if (!merged) {
			mergeWithEnvironment();
		}

		// 优先环境变量
		String value = System.getProperty(key);
		if (StringUtils.isNotBlank(value)) return value;

		value = System.getenv(key);
		if (StringUtils.isNotBlank(value)) return value;

		if (ALL_PROPERTIES.containsKey(key)) {
			return ALL_PROPERTIES.getProperty(key);
		}

		return defaultValue;
	}

	public static int getInt(String key) {
		return getInt(key, 0);
	}

	public static int getInt(String key, int defalutValue) {
		String v = getProperty(key);
		if (v != null) return Integer.parseInt(v);
		return defalutValue;
	}

	public static long getLong(String key) {
		return getLong(key, 0L);
	}

	public static long getLong(String key, long defalutValue) {
		String v = getProperty(key);
		if (v != null) return Long.parseLong(v);
		return defalutValue;
	}

	public static boolean getBoolean(String key) {
		return Boolean.parseBoolean(getProperty(key));
	}

	public synchronized static void merge(Properties properties) {
		if (properties == null || properties.isEmpty()) return;

		Set<Entry<Object, Object>> entrySet = properties.entrySet();
		for (Entry<Object, Object> entry : entrySet) {
			Object value = null;
			try {
				value = envHelperGetPropertiesMethod.invoke(null, entry.getKey());
			} catch (Exception e) {
			}
			if (value == null) value = entry.getValue();
			if (value != null) {
				ALL_PROPERTIES.setProperty(entry.getKey().toString(), value.toString());
			}
		}
	}

	public synchronized static void add(String key, String value) {
		if (StringUtils.isAnyBlank(key, value)) return;
		addToProperties(key, value);
	}

	public static boolean containsProperty(String key) {
		return ALL_PROPERTIES.containsKey(key);
	}

	/**
	 * @desc 如果替换包含占位符则替换占位符
	 * @param key 属性文件的key
	 * @return
	 */
	private static String addToProperties(String key, String value) {

		if (!value.contains(PLACEHOLDER_PREFIX)) {
			ALL_PROPERTIES.put(key, value);
			return value;
		}

		String[] segments = value.split("\\$\\{");
		String seg;

		StringBuilder finalValue = new StringBuilder();
		for (int i = 0; i < segments.length; i++) {
			seg = StringUtils.trimToNull(segments[i]);
			if (StringUtils.isBlank(seg)) continue;

			if (seg.contains(PLACEHOLDER_SUFFIX)) {
				String refKey = seg.substring(0, seg.indexOf(PLACEHOLDER_SUFFIX)).trim();
				// 其他非${}的占位符如：{{host}}
				String withBraceString = null;
				if (seg.contains("{")) {
					withBraceString = seg.substring(seg.indexOf(PLACEHOLDER_SUFFIX) + 1);
				}

				// 如果包含默认值，如：${host:127.0.0.1}
				String defaultValue = null;
				if (refKey.contains(":")) {
					String[] tmpArray = refKey.split(":");
					refKey = tmpArray[0];
					defaultValue = tmpArray[1];
				}

				String refValue = getProperty(refKey);
				if (StringUtils.isBlank(refValue)) {
					refValue = defaultValue;
				}
				finalValue.append(refValue);

				if (withBraceString != null) {
					finalValue.append(withBraceString);
				} else {
					String[] segments2 = seg.split("\\}");
					if (segments2.length == 2) {
						finalValue.append(segments2[1]);
					}
				}
			} else {
				finalValue.append(seg);
			}
		}

		ALL_PROPERTIES.put(key, finalValue.toString());

		return finalValue.toString();
	}
}
